package redis_lock

import (
	"context"
	"errors"
	"time"
	_ "embed"
	redis "github.com/go-redis/redis/v9"
	"github.com/google/uuid"
)

type Client struct {
	client redis.Cmdable
}

func NewClient(c redis.Cmdable) *Client{
	return &Client{
		client: c,
	}
}


/****************
v1版本的缺点：
1. 解锁的时候并不判断是否是自己加的锁，可能把其他的人加的锁给解开了
2. 并不能保证锁的唯一性
*****************/
func (c *Client) LockV1(ctx context.Context, key string, timeout time.Duration) error {
	resp, err := c.client.SetNX(ctx, key, "123", timeout).Result()
	if err != nil { //接口报错就直接返回错误
		return err
	}

	if !resp {
		return errors.New("加锁失败")
	}
	return nil
}

func (c *Client) UnlockV1(ctx context.Context, key string) error {
	resp, err := c.client.Del(ctx, key).Result()
	if err != nil {
		return err
	}
	if resp != 1 {
		//1. 过期
		//2. 被人删了
		return errors.New("解锁失败")
	}
	return nil
}

//v2 版本优化,value是有意义的，并且直接返回一个Locker出去

func (c *Client) LockV2(ctx context.Context, key string, timeOut time.Duration) (*Locker, error) {
	val := uuid.New().String() 
	resp, err := c.client.SetNX(ctx, key, val, timeOut).Result()
	if err != nil {
		return nil, err
	}

	if !resp {
		return nil, errors.New("加锁失败")
	}

	return newLocker(c.client, key, val), nil
}

type Locker struct {
	val string 
	key string
	client redis.Cmdable
}

var (
	//go:embed  unlock.lua
	luaUnlock string
	ErrLockNotHold = errors.New("未持有锁")
)


func newLocker(c redis.Cmdable, key, val string) *Locker{
	return &Locker{
		key: key,
		val: val,
		client: c,
	}
}

//将unlock方法给到Locker上面去,这个方法有原子性上面的问题
func (l *Locker) UnlockV2(ctx context.Context) error {
	val, err := l.client.Get(ctx, l.key).Result()
	if err != nil {
		return err
	}

	if val == l.val {
		_, err := l.client.Del(ctx, l.key)
		if err != nil {
			return err
		}
	}

	return nil 
} 

//使用lua脚本来解决原子性的问题
func (l *Locker) UnlockV3(ctx context.Context) error {
	res, err := l.client.Eval(ctx, luaUnlock, []string{l.key}, l.val).Int64()
	if err != nil {
		return err
	}

	if res == 0 {
		//说明这把锁不是自己的，或者这个key不存在
		return ErrLockNotHold
	}
	return nil 
}